/**************************************************************************//**
*
* @copyright (C) 2020 Nuvoton Technology Corp. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date            Author         Notes
* 2020-11-11      Wayne          First version
******************************************************************************/
#include <rtconfig.h>

#if defined( BSP_USING_I2C)

#include <rtdevice.h>
#include "drv_i2c.h"
#include "drv_sys.h"
#include "nuc970_mfp.h"

/* Private define ---------------------------------------------------------------*/
#define LOG_TAG    "drv.i2c"
#define DBG_ENABLE
#define DBG_SECTION_NAME   LOG_TAG
#define DBG_LEVEL DBG_ERROR
#define DBG_COLOR
#include <drv_log.h>

#if !defined(BSP_USING_I2C0) && !defined(BSP_USING_I2C1)
#error "Please define at least one BSP_USING_SOFT_I2Cx"
/* this driver can be disabled at menuconfig → RT-Thread Components → Device Drivers */
#endif

/* Private typedef --------------------------------------------------------------*/


/* Private variables ------------------------------------------------------------*/

static i2c_config nu_i2c_config[ ] =
{
#if defined(BSP_USING_I2C0)
    I2C0_BUS_CONFIG,
#endif
#if defined(BSP_USING_I2C1)
    I2C1_BUS_CONFIG,
#endif
};
static struct rt_soft_i2c_bus i2c_obj[sizeof(nu_i2c_config) / sizeof(nu_i2c_config[0])];

/* Private functions ------------------------------------------------------------*/
static rt_bool_t i2c_bus_isbusy(i2c_config *cfg)
{
    if (((cfg->I2C->SWR & 0x18) == 0x18
        && (cfg->I2C->CSR & I2C_CSR_BUSY_Msk) == 0)) {
        
        return RT_FALSE;
    }

    return RT_TRUE;
}

static inline rt_err_t i2c_wait_transmit_complete(i2c_config *cfg)
{
    rt_tick_t start = rt_tick_get();
    while ((cfg->I2C->CSR & I2C_CSR_TIP_Msk))
    {
        if ((rt_tick_get() - start) > cfg->timeout)
        {
            return -RT_ETIMEOUT;
        }
    }

    return RT_EOK;
}

static inline rt_err_t i2c_write_data(i2c_config *cfg, rt_uint8_t data, rt_bool_t stop)
{
    cfg->I2C->TxR = data;
    if (stop == RT_TRUE) {
        cfg->I2C->CMDR = I2C_CMDR_STOP_Msk | I2C_CMDR_WRITE_Msk;
    } else {
        cfg->I2C->CMDR = I2C_CMDR_WRITE_Msk;
    }
    return i2c_wait_transmit_complete(cfg);
}

static inline rt_err_t i2c_read_data(i2c_config *cfg, rt_uint8_t *data, rt_bool_t stop)
{
    *data = cfg->I2C->RxR;
    if (stop == RT_TRUE) {
        cfg->I2C->CMDR = I2C_CMDR_STOP_Msk | I2C_CMDR_READ_Msk | I2C_CMDR_NACK_Msk;
    } else {
        cfg->I2C->CMDR = I2C_CMDR_READ_Msk;
    }
    return i2c_wait_transmit_complete(cfg);
}

static rt_err_t i2c_write_address(i2c_config *cfg,
                                    struct rt_i2c_msg  *msg)
{
    rt_uint16_t flags = msg->flags;
    rt_uint16_t ignore_nack = msg->flags & RT_I2C_IGNORE_NACK;
    rt_uint8_t addr1, addr2;
    rt_err_t ret;


    if (flags & RT_I2C_ADDR_10BIT)
    {
        addr1 = 0xF0 | ((msg->addr >> 7) & 0x06);
        addr2 = msg->addr & 0xFF;

        LOG_D("address1: %d, address2: %d\n", addr1, addr2);

        /* Send device address */
        cfg->I2C->TxR = addr1;
        if (i2c_bus_isbusy(cfg)) return -RT_EBUSY;

        cfg->I2C->CMDR = I2C_CMDR_START_Msk | I2C_CMDR_WRITE_Msk;  // start write

        ret = i2c_wait_transmit_complete(cfg);
        if (ret != RT_EOK) /* for timeout condition */
            return -RT_EIO;
        if ((cfg->I2C->CMDR | I2C_CSR_RxACK_Msk) == I2C_CSR_RxACK_Msk
            && !ignore_nack) {
            return -RT_EIO;
        }

        cfg->I2C->TxR = addr2;
        cfg->I2C->CMDR = I2C_CMDR_WRITE_Msk;  // write
        if ((cfg->I2C->CSR | I2C_CSR_RxACK_Msk) == I2C_CSR_RxACK_Msk
            && !ignore_nack) {
            return -RT_EIO;
        }

        if (flags & RT_I2C_RD)
        {
            addr1 |= 0x01;
            cfg->I2C->TxR = addr1;
            cfg->I2C->CMDR = I2C_CMDR_START_Msk | I2C_CMDR_WRITE_Msk;  // start write
            if ((cfg->I2C->CSR | I2C_CSR_RxACK_Msk) == I2C_CSR_RxACK_Msk
                && !ignore_nack) {
                return -RT_EIO;
            }
        }
    }
    else
    {
        /* 7-bit addr */
        addr1 = msg->addr << 1;
        if (flags & RT_I2C_RD)
            addr1 |= 1;

        /* Send device address */
        cfg->I2C->TxR = addr1;
        if (i2c_bus_isbusy(cfg)) return -RT_EBUSY;

        cfg->I2C->CMDR = I2C_CMDR_START_Msk | I2C_CMDR_WRITE_Msk;  // start write

        ret = i2c_wait_transmit_complete(cfg);
        if (ret != RT_EOK) /* for timeout condition */
            return -RT_EIO;
        if ((cfg->I2C->CSR | I2C_CSR_RxACK_Msk) == I2C_CSR_RxACK_Msk
            && !ignore_nack) {
            return -RT_EIO;
        }
    }

    return RT_EOK;
}

static rt_size_t i2c_master_xfer(struct rt_i2c_bus_device *bus,
                                 struct rt_i2c_msg         msgs[],
                                 rt_uint32_t               num)
{
    struct rt_i2c_msg *msg;
    i2c_config *cfg = (i2c_config *)bus->priv;
    rt_size_t i;
    rt_uint32_t cnt_data;
    rt_err_t ret;

    RT_ASSERT(bus != RT_NULL);

    if (num == 0) return 0;

    if (i2c_bus_isbusy(cfg)) {
        return 0;
    }

    cfg->I2C->CSR |= I2C_CSR_I2CEN_Msk;

    for (i = 0; i < num; i++)
    {
        msg = &msgs[i];

        if (!(msg->flags & RT_I2C_NO_START)) /* Address */
        {
            ret = i2c_write_address(cfg, msg);
            if (RT_EOK != ret)
            {
                i = 0;
                LOG_E("Send Address Fail");
                break;
            }
        }

        if (msg->flags & RT_I2C_RD) /* Receive Bytes */
        {
            ret = i2c_wait_transmit_complete(cfg);
            if (ret != RT_EOK) /* for timeout condition */
                return -RT_EIO;
            for (cnt_data = 0 ; cnt_data < (msg->len) ; cnt_data++)
            {
                msg->buf[cnt_data] = cfg->I2C->RxR;
                if (cnt_data < (msg->len-1)) {
                    i2c_read_data(cfg, &msg->buf[cnt_data], RT_FALSE);  // read
                } else {
                    i2c_read_data(cfg, &msg->buf[cnt_data], RT_TRUE);  // read
                }
                if (ret != RT_EOK) /* for timeout condition */
                    break;
            }
        }
        else /* Send Bytes */
        {
            for (cnt_data = 0 ; cnt_data < (msg->len) ; cnt_data++)
            {
                /* Send register number and MSB of data */
                if (cnt_data < (msg->len-1)) {
                    ret = i2c_write_data(cfg, (uint8_t)(msg->buf[cnt_data]), RT_FALSE);
                } else {
                    ret = i2c_write_data(cfg, (uint8_t)(msg->buf[cnt_data]), RT_TRUE);
                }
                if (ret != RT_EOK) /* for timeout condition */
                    break;
            }
        }
    }

    cfg->I2C->CMDR = I2C_CMDR_STOP_Msk;

    cfg->I2C->CSR = 0;
    return i;
}

static rt_err_t i2c_bus_control(struct rt_i2c_bus_device *bus,
                                rt_uint32_t               cmd,
                                void                     *args)
{
    i2c_config *cfg = (i2c_config *)bus->priv;
    rt_uint32_t clock;
    
    switch(cmd) {
    case RT_I2C_DEV_CTRL_CLK:
        clock = (rt_uint32_t)args;
        if (clock > (SYS_CLOCK_PCLK / 10)) {
            clock = 100;
        }
        cfg->I2C->DIVIDER = SYS_CLOCK_PCLK / (5*clock) - 1;
    break;
    default:
    break;
    }

    return RT_EOK;
}
static const struct rt_i2c_bus_device_ops nu_i2c_ops =
{
    .master_xfer     = i2c_master_xfer,
    .i2c_bus_control = i2c_bus_control,
};

static void nu_i2c_isr(int vector, void *param)
{
    I2C_TypeDef *I2C = (I2C_TypeDef *)param;
    rt_uint32_t reg;

    reg = I2C->CSR;
    reg |= I2C_CSR_IF_Msk;
    I2C->CSR = reg;
}

/* Public functions -------------------------------------------------------------*/
int rt_hw_i2c_init(void)
{
    rt_size_t obj_num = sizeof(nu_i2c_config) / sizeof(i2c_config);
    rt_err_t result = RT_EOK;
    int i;

    for (i = 0; i < obj_num; i++)
    {
        MFP_I2CInit(nu_i2c_config[i].I2C);

        rt_hw_interrupt_install(nu_i2c_config[i].irq_type, nu_i2c_isr, (void *)(nu_i2c_config[i].I2C), "i2c");
        rt_hw_interrupt_set_type(nu_i2c_config[i].irq_type, HIGH_LEVEL_SENSITIVE);

        i2c_obj[i].i2c_bus.ops = &nu_i2c_ops;
        i2c_obj[i].i2c_bus.priv = &nu_i2c_config[i];

        result = rt_i2c_bus_device_register(&i2c_obj[i].i2c_bus, nu_i2c_config[i].bus_name);
        if (result != RT_EOK) {
            MFP_I2CDeInit(nu_i2c_config[i].I2C);
        }
        i2c_bus_control(&i2c_obj[i].i2c_bus, RT_I2C_DEV_CTRL_CLK, (void*)100);
    }

    return result;
}
INIT_DEVICE_EXPORT(rt_hw_i2c_init);

#endif /* BSP_USING_I2C */
